﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using System.Web.Services.Description;
using System.Xml;

namespace Provider
{
    internal class WebServiceInvoker
    {
        Assembly _webServiceAssembly;
        List<string> _services;
        Dictionary<string, Type> _availableTypes;

        public List<string> AvailableServices
        {
            get { return _services; }
        }

        public WebServiceInvoker(Uri webServiceUri)
        {
            _services = new List<string>();
            _availableTypes = new Dictionary<string, Type>();

            _webServiceAssembly = BuildAssemblyFromWSDL(webServiceUri);

            Type[] types = _webServiceAssembly.GetExportedTypes();

            foreach (Type type in types)
            {
                _services.Add(type.FullName);
                _availableTypes.Add(type.FullName, type);
            }
        }

        public List<string> EnumerateServiceMethods(string serviceName)
        {
            List<string> methods = new List<string>();

            if (!_availableTypes.ContainsKey(serviceName))
            {
                throw new Exception("Service Not Available");
            }
            else
            {
                Type type = _availableTypes[serviceName];

                //// Get method with appropriate type
                foreach (MethodInfo minfo in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                {
                    methods.Add(minfo.Name);
                }

                return methods;
            }
        }

        /// <summary>
        /// Invoke ws method
        /// T is whatever type
        /// </summary>
        public T InvokeMethod<T>(string serviceName, string methodName, params object[] args)
        {
            //// Create instance
            object obj = _webServiceAssembly.CreateInstance(serviceName);

            ///Get type of method
            Type type = obj.GetType();

            return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);
        }

        private Assembly BuildAssemblyFromWSDL(Uri webServiceUri)
        {
            //// Check url is not empty or null.
            if (String.IsNullOrEmpty(webServiceUri.ToString()))
            {
                throw new Exception("Không tìm thấy web service");
            }

            XmlTextReader xmlreader = new XmlTextReader(webServiceUri.ToString());
            ServiceDescriptionImporter descriptionImporter = BuildServiceDescriptionImporter(xmlreader);

            return CompileAssembly(descriptionImporter);
        }

        private ServiceDescriptionImporter BuildServiceDescriptionImporter(XmlTextReader xmlreader)
        {
            //// Check valid wsdl
            if (!ServiceDescription.CanRead(xmlreader))
            {
                throw new Exception("Invalid Web Service Description");
            }

            //// Parse wsdl to ServiceDescription
            ServiceDescription serviceDescription = ServiceDescription.Read(xmlreader);

            //// Create ServiceDescriptionImporter
            ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
            descriptionImporter.ProtocolName = "Soap";
            descriptionImporter.AddServiceDescription(serviceDescription, null, null);
            descriptionImporter.Style = ServiceDescriptionImportStyle.Client;
            descriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;

            return descriptionImporter;
        }

        private Assembly CompileAssembly(ServiceDescriptionImporter descriptionImporter)
        {
            //// Namespace
            CodeNamespace codeNamespace = new CodeNamespace();
            CodeCompileUnit codeUnit = new CodeCompileUnit();

            codeUnit.Namespaces.Add(codeNamespace);

            ServiceDescriptionImportWarnings importWarnings = descriptionImporter.Import(codeNamespace, codeUnit);

            //// Check wsdl is valid
            if (importWarnings == 0)
            {
                //// Choose language
                CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");
                string[] references = { "System.Web.Services.dll", "System.Xml.dll", "System.Data.dll" };

                //// Add some dll to run ws
                CompilerParameters parameters = new CompilerParameters(references);
                CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit);

                //// Throw exception
                foreach (CompilerError oops in results.Errors)
                {
                    throw new Exception("Compilation Error Creating Assembly");
                }

                return results.CompiledAssembly;
            }
            else
            {
                throw new Exception("Invalid WSDL");
            }
        }

        private object GetParameter(string value, string type)
        {
            object obj = null;
            switch (type)
            {
                case "string":
                    obj = value;
                    break;
                case "int":
                    obj = int.Parse(value);
                    break;
                case "float":
                    obj = float.Parse(value);
                    break;
                case "bool":
                    obj = bool.Parse(value);
                    break;
            }

            return obj;
        }
    }
}